package se.spagettikod.optimist.gwt.server;

import java.io.IOException;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import se.spagettikod.optimist.LockedByAnotherUserException;
import se.spagettikod.optimist.ModifiedByAnotherUserException;
import se.spagettikod.optimist.RemovedByAnotherUserException;

import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

@SuppressWarnings("serial")
public class OptimistRemoteServiceServlet extends RemoteServiceServlet {

	private static final Logger log = LoggerFactory.getLogger(Optimist.class);

	protected transient ThreadLocal<SqlSession> perThreadSqlSession;

	private void openSession() {
		synchronized (this) {
			if (perThreadSqlSession == null) {
				log.debug("Initializing ThreadLocal<SqlSession>, this should occur once.");
				perThreadSqlSession = new ThreadLocal<SqlSession>();
			}
			ServletContext ctx = super.getServletContext();
			SqlSessionFactory factory = (SqlSessionFactory) ctx
					.getAttribute(Optimist.OPTIMIST_MYBATIS_SESSION_FACTORY_KEY);
			if (factory == null) {
				throw new RuntimeException(
						"SqlSessionFactory is null, did you call Optimist.init() before using Optimist?");
			}
			perThreadSqlSession.set(factory.openSession());
			log.debug("Opened session " + perThreadSqlSession.get().hashCode());
		}
	}

	private void closeSession() {
		if (perThreadSqlSession != null && perThreadSqlSession.get() != null) {
			if (log.isDebugEnabled()) {
				log.debug("Closing session "
						+ perThreadSqlSession.get().hashCode());
			}
			perThreadSqlSession.get().close();
			perThreadSqlSession.set(null);
		} else {
			log.debug("Could not close session, was it initialized properly?");
		}
	}

	@Override
	protected void onAfterResponseSerialized(String serializedResponse) {
		super.onAfterResponseSerialized(serializedResponse);
		closeSession();
	}

	@Override
	protected void doUnexpectedFailure(Throwable e) {
		if (perThreadSqlSession != null && perThreadSqlSession.get() != null) {
			perThreadSqlSession.get().rollback();
		}
		closeSession();
		Throwable cause = e;
		while (cause != null) {
			if (cause instanceof ModifiedByAnotherUserException) {
				try {
					getThreadLocalResponse().setStatus(
							HttpServletResponse.SC_CONFLICT);
					getThreadLocalResponse().getWriter().print(
							ModifiedByAnotherUserException.class.getName());
					return;
				} catch (IOException e1) {
					super.doUnexpectedFailure(e);
				}
			} else if (cause instanceof LockedByAnotherUserException) {
				try {
					getThreadLocalResponse().setStatus(
							HttpServletResponse.SC_CONFLICT);
					getThreadLocalResponse().getWriter().print(
							LockedByAnotherUserException.class.getName());
					return;
				} catch (IOException e1) {
					super.doUnexpectedFailure(e);
				}
			} else if (cause instanceof RemovedByAnotherUserException) {
				try {
					getThreadLocalResponse().setStatus(
							HttpServletResponse.SC_GONE);
					getThreadLocalResponse().getWriter().print(
							RemovedByAnotherUserException.class.getName());
					return;
				} catch (IOException e1) {
					super.doUnexpectedFailure(e);
				}
			}
			cause = cause.getCause();
		}
		super.doUnexpectedFailure(e);
	}

	@Override
	protected void onAfterRequestDeserialized(RPCRequest rpcRequest) {
		super.onAfterRequestDeserialized(rpcRequest);
		openSession();
	}

}
